// Copyright (C) 2022 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
  "errors"
  "fmt"
  "io/ioutil"
  "reflect"
  "strconv"
  "strings"
)

func LoadConfigFile(filename string, dataStruct interface{}) error {
  allMap, err := readDataToMap(filename)
  if err != nil {
    return err
  }
  valueOf := reflect.ValueOf(dataStruct)
  typeOf := reflect.TypeOf(dataStruct)
  for index := 0; index < typeOf.Elem().NumField(); index++ {
    field := typeOf.Elem().Field(index)
    iniTag := field.Tag.Get("ini")
    if iniTag == "" {
      continue
    }
    item, errorNo := allMap[iniTag]
    if !errorNo {
      continue
    }
    value := valueOf.Elem().FieldByName(field.Name)
    if value.Kind() != reflect.Struct {
      continue
    }
    valueType := value.Type()
    for index := 0; index < valueType.NumField(); index++ {
      sField := valueType.Field(index)
      structIniTag := sField.Tag.Get("ini")
      if structIniTag == "" {
        continue
      }
      res, errorNo := item[structIniTag]
      if !errorNo {
        continue
      }
      fieldObj := value.FieldByName(sField.Name)
      setConfigValue(&fieldObj, res)
    }
  }
  return nil
}

func readDataToMap(filename string) (map[string]map[string]string, error) {
  fileByte, error := ioutil.ReadFile(filename)
  if error != nil {
    return nil, error
  }
  var dataMap = make(map[string]map[string]string, 10)
  contents := string(fileByte)
  strRow := strings.Split(contents, "\n")
  var currentRow = ""
  for _, rowContent := range strRow {
    if skipComments(rowContent) {
      continue
    }
    rowContent = strings.TrimSpace(rowContent)
    if strings.HasPrefix(rowContent, "[") && strings.HasSuffix(rowContent, "]") {
      rowContent = strings.Trim(rowContent, "[")
      rowContent = strings.Trim(rowContent, "]")
      rowContent = strings.ReplaceAll(rowContent, " ", "")
      _, errorNo := dataMap[rowContent]
      if errorNo {
        continue
      }
      currentRow = rowContent
      dataMap[rowContent] = make(map[string]string, 5)
      continue
    }
    if len(currentRow) == 0 {
      continue
    }
    index := strings.Index(rowContent, "=")
    if index == -1 {
      continue
    }
    runes := []rune(rowContent)
    key := string(runes[:index])
    key = strings.ReplaceAll(key, " ", "")
    value := string(runes[index+1:])
    dataMap[currentRow][key] = value
  }
  return dataMap, nil
}

func skipComments(lineContents string) bool {
  if len(lineContents) == 0 {
    return true
  }
  return strings.HasPrefix(lineContents, "*") || strings.HasPrefix(lineContents, "#") || strings.HasPrefix(lineContents, "//")
}
func setConfigValue(config *reflect.Value, value interface{}) error {
  configValue := value.(string)
  switch config.Kind() {
  case reflect.String:
    config.SetString(configValue)
  case reflect.Int:
    intValue, err := strconv.Atoi(configValue)
    if err != nil {
      return err
    }
    config.SetInt(int64(intValue))
  case reflect.Bool:
    booleanValue, err := strconv.ParseBool(configValue)
    if err != nil {
      return err
    }
    config.SetBool(booleanValue)
  default:
    return errors.New("Missing type conversion [ " + fmt.Sprintf("%v", config.Kind()) + " ]")
  }
  return nil
}
